package com.karaoke.android.player.videocache.file;

import com.karaoke.android.player.videocache.Cache;
import com.karaoke.android.player.videocache.ProxyCacheException;
import com.karaoke.android.player.videocache.file.*;
import com.karaoke.android.player.videocache.file.Files;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

/**
 * {@link Cache} that uses file for storing data.
 *
 * @author Alexey Danilov (danikula@gmail.com).
 */
public class FileCache implements Cache
{

    private static final String TEMP_POSTFIX = ".download";

    private final com.karaoke.android.player.videocache.file.DiskUsage diskUsage;
    public        File                                                 file;
    private       RandomAccessFile                                     dataFile;

    public FileCache(File file) throws ProxyCacheException
    {
        this(file, new UnlimitedDiskUsage());
    }

    public FileCache(File file, com.karaoke.android.player.videocache.file.DiskUsage diskUsage)
            throws ProxyCacheException
    {
        try
        {
            if (diskUsage == null)
            {
                throw new NullPointerException();
            }
            this.diskUsage = diskUsage;
            File directory = file.getParentFile();
            com.karaoke.android.player.videocache.file.Files.makeDir(directory);
            boolean completed = file.exists();
            this.file = completed ? file : new File(file.getParentFile(), file.getName() +
                    TEMP_POSTFIX);
            this.dataFile = new RandomAccessFile(this.file, completed ? "r" : "rw");
        }
        catch (IOException e)
        {
            throw new ProxyCacheException("Error using file " + file + " as disc cache", e);
        }
    }

    @Override
    public synchronized long available() throws ProxyCacheException
    {
        try
        {
            return (int) dataFile.length();
        }
        catch (IOException e)
        {
            throw new ProxyCacheException("Error reading length of file " + file, e);
        }
    }

    @Override
    public synchronized int read(byte[] buffer, long offset, int length) throws ProxyCacheException
    {
        try
        {
            dataFile.seek(offset);
            return dataFile.read(buffer, 0, length);
        }
        catch (IOException e)
        {
            String format = "Error reading %d bytes with offset %d from file[%d bytes] to " +
                    "buffer[%d bytes]";
            throw new ProxyCacheException(String.format(format, length, offset, available(),
                    buffer.length), e);
        }
    }

    @Override
    public synchronized void append(byte[] data, int length) throws ProxyCacheException
    {
        try
        {
            if (isCompleted())
            {
                throw new ProxyCacheException("Error append cache: cache file " + file + " is " +
                        "completed!");
            }
            dataFile.seek(available());
            dataFile.write(data, 0, length);
        }
        catch (IOException e)
        {
            String format = "Error writing %d bytes to %s from buffer with size %d";
            throw new ProxyCacheException(String.format(format, length, dataFile, data.length), e);
        }
    }

    @Override
    public synchronized void close() throws ProxyCacheException
    {
        try
        {
            dataFile.close();
            diskUsage.touch(file);
        }
        catch (IOException e)
        {
            throw new ProxyCacheException("Error closing file " + file, e);
        }
    }

    @Override
    public synchronized void complete() throws ProxyCacheException
    {
        if (isCompleted())
        {
            return;
        }

        close();
        String  fileName      = file.getName().substring(0, file.getName().length() -
                TEMP_POSTFIX.length());
        File    completedFile = new File(file.getParentFile(), fileName);
        boolean renamed       = file.renameTo(completedFile);
        if (!renamed)
        {
            throw new ProxyCacheException("Error renaming file " + file + " to " + completedFile
                    + " for completion!");
        }
        file = completedFile;
        try
        {
            dataFile = new RandomAccessFile(file, "r");
            diskUsage.touch(file);
        }
        catch (IOException e)
        {
            throw new ProxyCacheException("Error opening " + file + " as disc cache", e);
        }
    }

    @Override
    public synchronized boolean isCompleted()
    {
        return !isTempFile(file);
    }

    /**
     * Returns file to be used fo caching. It may as original file passed in constructor as some temp file for not completed cache.
     *
     * @return file for caching.
     */
    public File getFile()
    {
        return file;
    }

    private boolean isTempFile(File file)
    {
        return file.getName().endsWith(TEMP_POSTFIX);
    }

}
